﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Lime49;

namespace LockCrypt.Core {
    public class AccountBase : IAccount, IComparable {
        public virtual string ID { get; set; }
        public virtual string Name { get; set; }
        public virtual string ParentID { get; set; }
        public virtual ITemplate Template { get; set; }
        public virtual DateTime Created { get; set; }
        public virtual DateTime Modified { get; set; }
        public virtual string Icon { get; set; }
        public virtual IList<IField> Fields { get; set; }
        public virtual string Notes { get; set; }

        public AccountBase() {
            Created = DateTime.Now;
            Modified = DateTime.Now;
            Fields = new List<IField>();
            ID = Lime49.Utils.GenID(true);
            Icon = string.Empty;
        }

        /// <summary>
        /// Creates a deep copy of this account.
        /// </summary>
        /// <returns>A clone of this account.</returns>
        public virtual IAccount Clone() {
            AccountBase clone = new AccountBase() {
                Created = this.Created,
                Icon = this.Icon,
                ID = this.ID,
                Modified = this.Modified,
                Name = this.Name,
                Notes = this.Notes,
                ParentID = this.ParentID,
                Template = this.Template.Clone()
            };
            foreach(IField field in this.Fields) {
                clone.Fields.Add(field.Clone());
            }
            return clone;
        }

        /// <summary>
        /// Derives a template from the fields in this account.
        /// </summary>
        /// <returns>An equivalent template for this account.</returns>
        public virtual ITemplate DeriveTemplate() {
            TemplateBase template = new TemplateBase() {
                CanUserEdit = false,
                Name = I18nUtils.GetString("Strings", "TemplateFmt", this.Name),
                ShowNotes = true
            };
            foreach(IField field in this.Fields) {
                template.Fields.Add(field.Clone());
            }
            return template;
        }

        /// <summary>
        /// Converts this account to an XElement.
        /// </summary>
        /// <returns>An XElement representing this account.</returns>
        public XElement ToXElement() {
            XElement xFields = new XElement("fields");
            foreach(IField field in Fields) {
                try {
                    xFields.Add(field.ToXElement());
                } catch(Exception ex) {
                    Console.WriteLine("Error exporting field: {0}", ex.Message);
                }
            }
            XElement xAccount = new XElement("account",
                new XElement("title", HtmlUtils.HtmlEncode(this.Name)),
                new XElement("creationtime", this.Created.ToString("s")),
                new XElement("lastmodtime", this.Modified.ToString("s")),
                new XElement("icon", HtmlUtils.HtmlEncode(this.Icon)),
                new XAttribute("id", HtmlUtils.HtmlEncode(this.ID)),
                new XElement("notes", HtmlUtils.HtmlEncode(this.Notes)),
                new XElement("parentid", HtmlUtils.HtmlEncode(this.ParentID)),
                xFields);
            if(this.Template == null) {
                xAccount.Add(new XElement("typeid", string.Empty));
                xAccount.Add(new XElement("shownotes", true));
            } else {
                xAccount.Add(new XElement("typeid", HtmlUtils.HtmlEncode(this.Template.ID)));
                xAccount.Add(new XElement("shownotes", HtmlUtils.HtmlEncode(this.Template.ShowNotes.ToString())));
            }
            return xAccount;
        }

        /// <summary>
        /// Initializes a new <see cref="AccountBase"/> from an XElement.
        /// </summary>s
        /// <param name="element">The XElement containing the account data.</param>
        /// <returns>The account contained in the specified XElement.</returns>
        public static IAccount FromXElement(XElement element) {
            try {
                string parentId = HtmlUtils.HtmlDecode(element.Element("parentid").Value);
                IAccount account = new AccountBase() {
                    Name = HtmlUtils.HtmlDecode(element.Element("title").Value),
                    Created = DateTime.Parse((element.Element("creationtime") ?? new XElement("creationtime", DateTime.Now.ToString("s"))).Value),
                    Modified = DateTime.Parse((element.Element("lastmodtime") ?? new XElement("lastmodtime", DateTime.Now.ToString("s"))).Value),
                    Icon = HtmlUtils.HtmlDecode(element.Element("icon").Value),
                    ID = HtmlUtils.HtmlDecode(element.Attribute("id").Value),
                    Notes = HtmlUtils.HtmlDecode(element.Element("notes").Value),
                    ParentID = string.IsNullOrEmpty(parentId) ? null : parentId,
                    Template = new TemplateBase() {
                        ID = HtmlUtils.HtmlDecode(element.Element("typeid").Value),
                        ShowNotes = bool.Parse(element.Element("shownotes").Value),
                    }
                };
                foreach(XElement field in (element.Element("fields") ?? new XElement("fields")).Elements("field")) {
                    try {
                        account.Fields.Add(FieldBase.FromXElement(field));
                    } catch {
                        Console.WriteLine("Invalid field in account '{0}'", account.Name); // log it but the rest of the account data may be valid
                    }
                }
                return account;
            } catch { // usually NullReferenceException, but if something's invalid the account will be invalid
                throw new Exception("Invalid account");
            }
        }

        public override string ToString() {
            return string.Format("{0} ({1})", Name, ID);
        }

        public int CompareTo(object obj) {
            IAccount otherAccount = obj as IAccount;
            if(otherAccount == null) {
                return 1;
            } else {
                return string.Compare(this.Name, otherAccount.Name, StringComparison.InvariantCultureIgnoreCase);
            }
        }
    }
}
